package net.zoneland.knowledge.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Streams;
import net.zoneland.knowledge.config.TalkConfig;
import net.zoneland.knowledge.constant.BizConstant;
import net.zoneland.knowledge.constant.Constants;
import net.zoneland.knowledge.dao.WorkApplyInfoMapper;
import net.zoneland.knowledge.enums.*;
import net.zoneland.knowledge.exceptions.WorkApplyException;
import net.zoneland.knowledge.model.*;
import net.zoneland.knowledge.service.AttachmentService;
import net.zoneland.knowledge.service.EmailService;
import net.zoneland.knowledge.service.UniFlowService;
import net.zoneland.knowledge.service.WorkSubTaskInternalService;
import net.zoneland.knowledge.service.bean.ResultInfo;
import net.zoneland.knowledge.utils.LoggerHelper;
import net.zoneland.knowledge.utils.WorkApplyOperationUtils;
import net.zoneland.uniflow.client.TaskObject;
import net.zoneland.uniflow.client.UserInfo;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 分任务的内部服务接口。
 * <p>所谓内部服务，即不对上层（Controller）开放，只对其他服务层代码（XXService）提供公共服务</p>
 */
@Service
public class WorkSubTaskInternalServiceImpl implements WorkSubTaskInternalService {
    private static final Logger LOGGER = LoggerFactory.getLogger(WorkSubTaskInternalServiceImpl.class);

    /**
     * 工单信息数据接口.
     */
    @Autowired
    private WorkApplyInfoMapper workApplyInfoMapper;

    /**
     * 附件处理业务接口.
     */
    @Autowired
    private AttachmentService attachmentService;

    /**
     * 流程业务接口.
     */
    @Autowired
    private UniFlowService uniFlowService;

    @Autowired
    private EmailService<TaskObject[], WorkApplyInfo> emailService;

    @Autowired
    private TalkConfig talkConfig;

    /**
     * 在分任务确认或者退回后有待办的节点.
     */
    private static final EnumSet<WorkSubTaskNodeEnum> HAVE_TODO_NODE_AFTER_CONFIRM_OR_REJECT = EnumSet.of(WorkSubTaskNodeEnum.SUPERIOR_CONFIRM);

    /**
     * 可以上报的分任务状态.
     * 2022-11-11 排除了退回 （退回的情况要结合子工单分任务的状态）
     */
    private static final EnumSet<WorkSubTaskNodeEnum> CAN_REPORT_NODE = EnumSet.of(WorkSubTaskNodeEnum.WAITING_FOR_REPORT,
            WorkSubTaskNodeEnum.WAITING_FOR_HANDLE);

    /**
     * 上报完成状态.
     * 等待确认和已确认都算上报完成
     */
    private static final EnumSet<WorkSubTaskNodeEnum> REPORT_FINISH_NODE = EnumSet.of(WorkSubTaskNodeEnum.SUPERIOR_CONFIRM,
            WorkSubTaskNodeEnum.FINISH);

    /**
     * 创建工单流程服务线程池
     */
    @Resource(name = "createApplyThreadPoolExecutor")
    private ExecutorService createApplyThreadPoolExecutor;

    /**
     * 确认分任务。
     *
     * @param info         主工单对象
     * @param subApplyList 要确认分任务的子工单对象
     * @param allFinish    主工单下所有分任务是否都确认了
     */
    @Override
    public void confirmSubTasks(final WorkApplyInfo info, final List<WorkApplyInfo> subApplyList, final boolean allFinish) {
        // 先更新当前工单的分任务
        WorkSubTaskNodeEnum nextNodeForApply = allFinish ?
                WorkSubTaskNodeEnum.SUPERIOR_CONFIRM : WorkSubTaskNodeEnum.WAITING_FOR_CONFIRM;
        updateSubTasksNode(info, nextNodeForApply);

        // 更新子工单的分任务
        for (WorkApplyInfo subApply : subApplyList) {
            updateSubTasksNode(subApply, WorkSubTaskNodeEnum.FINISH);
        }
    }

    /**
     * 确认分任务，实质是更新分任务的状态及其父级分任务的状态。
     *
     * @param currentApplyInfo 当前工单（是需要确认的分任务的上级工单）
     * @param subTaskInfoList  要确认的分任务列表
     * @param user
     * @return 收到影响的父级分任务（并非每个父级分任务都会收到影响）
     */
    @Override
    public List<WorkSubTaskInfo> confirmSubTasks(final WorkApplyInfo currentApplyInfo, final List<WorkSubTaskInfo> subTaskInfoList,
                                                 final LoginUserPojo user) {
        // 收影响的父级分任务
        final List<WorkSubTaskInfo> impactedParentSubTask = Lists.newArrayList();

        // 先更新分任务的状态
        updateSubTasksNode(subTaskInfoList, WorkSubTaskNodeEnum.FINISH);

        // 父级分任务的Map，key是分任务id，便于下级分任务寻找上级
        final Map<String, WorkSubTaskInfo> parentSubTaskMap = currentApplyInfo.getSubTasks().stream().collect(Collectors.toMap(WorkSubTaskInfo::getId, subTask -> subTask));

        // 下面开始更新分任务的父任务的状态
        // 先找到所有父任务的ID、并去重
        final List<String> parentTaskIdList = subTaskInfoList.stream()
                .map(WorkSubTaskInfo::getParentTaskId)
                .distinct()
                .collect(Collectors.toList());
        // 遍历每一个父任务
        for (String parentTaskId : parentTaskIdList) {
            // 找到父级分任务对象
            final WorkSubTaskInfo parentSubTask = parentSubTaskMap.get(parentTaskId);
            // 塞到List里便于返回
            impactedParentSubTask.add(parentSubTask);

            // 根据父任务ID找到此父任务下的所有子任务
            final List<WorkSubTaskInfo> allBrotherSubTaskList = workApplyInfoMapper.getWorkSubTaskInfoByParentTaskId(parentTaskId);
            // 计算所有子任务的整体状态
            final WorkStatusOfSubTaskEnum workStatusOfSubTaskEnum = calculateWorkApplyStatusBySubTasks(allBrotherSubTaskList);

            // 当且仅当所有子任务都完成的时候（都确认过了），才需要把父级任务的状态改成可上报（父级工单是子工单）或者完成（父级工单是主工单）
            if (WorkStatusOfSubTaskEnum.SUBTASKS_ALL_FINISHED == workStatusOfSubTaskEnum) {
                updateSubTasksNode(Lists.newArrayList(parentSubTask),
                        StringUtils.equals(currentApplyInfo.getFlowType(), BizConstant.WISDOM_MAIN_PROCESS_CODE) ? WorkSubTaskNodeEnum.FINISH : WorkSubTaskNodeEnum.WAITING_FOR_REPORT);
            }
        }

        // 开始搞合并附件（合并的业务就是把下级分任务的上报附件合并到上级分任务的合并附件里）
        mergeAttachmentForConfirm(currentApplyInfo, parentSubTaskMap, subTaskInfoList, user);

        /*
         * 更新当前工单状态：
         * 1、如果当前工单是主工单，且所有分任务都完成了，需要流转到FINISH_CONFIRM状态
         * 2、如果当前工单是主工单，且有分任务还未完成，则停留在当前环节不变
         * 3、如果当前工单不是主工单，确认完之后还需要上报，事情还没做完，所以也停留在当前环节不变
         */
        if(StringUtils.equals(currentApplyInfo.getFlowType(), BizConstant.WISDOM_MAIN_PROCESS_CODE)
                && currentApplyInfo.getSubTasks().stream().allMatch(subTask -> WorkSubTaskNodeEnum.FINISH == subTask.getNode())) {
            // 只需要修改状态为FINISH_CONFIRM，不需要设置处理人（因为处理人还是当前人不变）
            currentApplyInfo.setStatus(WorkStatusEnum.FINISH_CONFIRM);
            currentApplyInfo.setUpdateTime(new Date());
            // 只需要保存工单对象本身即可，合并附件等已经在mergeAttachmentForConfirm方法里保存过了
            workApplyInfoMapper.updateWorkApplyInfo(currentApplyInfo);
        } else {
            checkNeedSuspendTask(currentApplyInfo, subTaskInfoList);
        }
        return impactedParentSubTask;
    }

    /**
     * 在确认分任务时合并附件。
     *
     * @param currentApplyInfo 上级分任务
     * @param subTaskInfoList  确认的下级分任务
     * @param user
     */
    private void mergeAttachmentForConfirm(final WorkApplyInfo currentApplyInfo,
                                           final Map<String, WorkSubTaskInfo> parentSubTaskMap,
                                           final List<WorkSubTaskInfo> subTaskInfoList,
                                           final LoginUserPojo user) {
        for(WorkSubTaskInfo subTask : subTaskInfoList) {
            // 找到上级分任务
            WorkSubTaskInfo parentSubTask = parentSubTaskMap.get(subTask.getParentTaskId());
            Preconditions.checkArgument(Objects.nonNull(parentSubTask),
                    String.format("找不到上级分任务，产生合并附件失败，分任务id[%s]标题[%s]", subTask.getId(), subTask.getName()));
            /*
             * 本次修改的原因：合并附件是N个子工单分任务附件依次同工单分任务模板合，分任务上报一个就跟之前的合并结果合
             * 这个有个问题，如果分任务存在退回的情况，之前已合并的内容没办法撤回修改，如果这种情况，要重新全所有子工单分任务的上报附件。
             */

            // 上级分任务要初始化合并附件，没有的话要生成出来
            initMergeAttachmentForSubTask(parentSubTask, user);
            // 下级分任务要初始化上报附件
            loadReportAttachments(subTask);
            List<WorkSubTaskInfo> confirmedSubTaskList = Lists.newArrayList(subTask);

            // 上报次数大于1时，有退回的情况
            if (subTask.getReportCount() > 1) {
                //如果是退回后重新上报 则需要重新合并所有上报的子任务
                //先把当前合并附件的内容设置成模板附件
                Streams.forEachPair(
                        parentSubTask.getTemplateAttachments().stream(),
                        parentSubTask.getMergeAttachments().stream(),
                        (template, merge) -> merge.setAttachment(template.getAttachment())
                );
                // 重新获取所有已上报的附件
                final List<WorkSubTaskInfo> allSubTaskInfo = workApplyInfoMapper.getWorkSubTaskInfoByParentTaskId(parentSubTask.getId());

                // 过滤排除当前分任务，且已经确认状态的分任务
                final List<WorkSubTaskInfo> satisfiedList = allSubTaskInfo.stream()
                        .filter(item -> !StringUtils.equals(subTask.getId(), item.getId()) && item.getNode() == WorkSubTaskNodeEnum.FINISH)
                        .collect(Collectors.toList());

                // 为每个分任务加载附件
                satisfiedList.forEach(taskInfo -> loadReportAttachments(taskInfo));

                // 需要合并的分任务是当前分任务和历史已完成分任务并集
                confirmedSubTaskList = ListUtils.union(Lists.newArrayList(subTask), satisfiedList);
            }
            // 再执行合并动作
            attachmentService.mergeResultAttachmentContentForSubTask(parentSubTask, confirmedSubTaskList);
            // 保存合并后的附件
            attachmentService.insertMergeAttachment(parentSubTask);
        }
    }

    private void initMergeAttachmentForSubTask(final WorkSubTaskInfo subTaskInfo, final LoginUserPojo user) {
        if (CollectionUtils.isEmpty(subTaskInfo.getMergeAttachments())) {
            // 这里肯定要再次查询模板附件信息了,参数传的是不带blob字段的
            final List<WorkApplyInfoAttachment> templateAttachmentList =
                    workApplyInfoMapper.findWorkApplyInfoAttachmentsBySubTaskId(subTaskInfo.getMainTaskId(), AttachTypeEnum.TEMPLATE);
            subTaskInfo.setTemplateAttachments(templateAttachmentList);
            List<WorkApplyInfoAttachment> mergeAttachmentList = templateAttachmentList.stream()
                    .map(a -> makeMergeAttachment(a, subTaskInfo, user)).collect(Collectors.toList());
            subTaskInfo.setMergeAttachments(mergeAttachmentList);
        }
    }

    private WorkApplyInfoAttachment makeMergeAttachment(final WorkApplyInfoAttachment templateAttachment, final WorkSubTaskInfo subTaskInfo, LoginUserPojo user) {
        return resetAttachment(templateAttachment, subTaskInfo, AttachTypeEnum.MERGE, user);
    }

    private WorkApplyInfoAttachment makeReportAttachmentByMergeAttachment(final WorkApplyInfoAttachment mergeAttachment, final WorkSubTaskInfo subTaskInfo, LoginUserPojo user) {
        return resetAttachment(mergeAttachment, subTaskInfo, AttachTypeEnum.RESULT, user);
    }

    /**
     * 重置附件，用来复制附件用。
     * @param attachment 需要重置（复制）的附件
     * @param subTaskInfo 分任务，需要设置相关字段进去
     * @param type 重置（复制）后的附件类型
     * @return
     */
    private WorkApplyInfoAttachment resetAttachment(final WorkApplyInfoAttachment attachment, final WorkSubTaskInfo subTaskInfo, AttachTypeEnum type, LoginUserPojo user) {
        WorkApplyInfoAttachment newAttachment = new WorkApplyInfoAttachment();
        BeanUtils.copyProperties(attachment, newAttachment);
        // id置空，否则不会保存
        newAttachment.setId(null);
        newAttachment.setWorkApplyId(subTaskInfo.getWorkApplyId());
        newAttachment.setSubTaskId(subTaskInfo.getId());
        newAttachment.setUploadTime(new Date());
        newAttachment.setType(type);
        if(Objects.nonNull(user)) {
            newAttachment.setUploaderUid(user.getUserID());
            newAttachment.setUploaderName(user.getUserName());
        }
        return newAttachment;
    }

    /**
     * 退回分任务，实质是更新分任务的状态。
     *
     * @param subTaskInfoList 要退回的分任务列表
     */
    @Override
    public void rejectSubTasks(final List<WorkSubTaskInfo> subTaskInfoList) {
        // 更新分任务的状态
        updateSubTasksNode(subTaskInfoList, WorkSubTaskNodeEnum.REJECT);
    }

    /**
     * 上报分任务。
     *
     * @param list 需要上报的分任务列表
     */
    public void reportSubTasks(final List<WorkSubTaskInfo> list) {
        final Date now = new Date();
        for (WorkSubTaskInfo workSubTaskInfo : list) {
            // 更新分任务：上级确认环节
            workSubTaskInfo.setNode(WorkSubTaskNodeEnum.SUPERIOR_CONFIRM);
            workSubTaskInfo.setState(WorkSubTaskNodeEnum.SUPERIOR_CONFIRM.getStateValue());
            workSubTaskInfo.setUpdateTime(now);
            // 更新内容刷到数据库
            workApplyInfoMapper.updateWorkSubTask(workSubTaskInfo);
            // 更新分任务的上报附件
            if(CollectionUtils.isNotEmpty(workSubTaskInfo.getReportAttachments())){
                attachmentService.insertReportAttachment(workSubTaskInfo);
            }

            // 更新父任务：等待确认环节
            if (StringUtils.isNotBlank(workSubTaskInfo.getParentTaskId())) {
                WorkSubTaskInfo parentSubTaskInfo = workApplyInfoMapper.getWorkSubTaskInfoById(workSubTaskInfo.getParentTaskId());
                parentSubTaskInfo.setNode(WorkSubTaskNodeEnum.WAITING_FOR_CONFIRM);
                parentSubTaskInfo.setState(WorkSubTaskNodeEnum.WAITING_FOR_CONFIRM.getStateValue());
                parentSubTaskInfo.setUpdateTime(now);
                // 更新完父任务要写到数据库去
                workApplyInfoMapper.updateWorkSubTask(parentSubTaskInfo);
            }
        }
    }

    /**
     * 更新指定工单下的所有分任务的状态。
     *
     * @param info       主工单对象，要把此工单下的所有分任务的状态都更新了
     * @param targetNode 要更新的状态
     */
    @Override
    public void updateSubTasksNode(final WorkApplyInfo info, final WorkSubTaskNodeEnum targetNode) {
        updateSubTasksNode(workApplyInfoMapper.findWorkSubTasksByApplyId(info.getId()), targetNode);
    }

    /**
     * 根据分任务状态来计算工单状态。
     * <p>
     * <li>有任何一个分任务在办理，工单就算办理状态</li>
     * <li>所有分任务都完成，工单才算完成</li>
     * <li>分任务已上报和已下发的混合状态（可能还有完成的），工单状态：等待上级确认和下级上报</li>
     * <li>分任务全部上报（可能还有完成的），工单状态：等待上级确认</li>
     * <li>分任务全部已下发（可能还有完成的），工单状态：等待下级上报</li>
     * </p>
     *
     * @param info 工单对象
     * @return 计算出来的工单状态
     */
    @Override
    public WorkStatusOfSubTaskEnum calculateWorkApplyStatusBySubTasks(final WorkApplyInfo info) {
        return calculateWorkApplyStatusBySubTasks(workApplyInfoMapper.findWorkSubTasksByApplyId(info.getId()));
    }

    /**
     * 根据分任务状态来计算工单状态。
     * <p>
     * <li>有任何一个分任务在办理，工单就算办理状态</li>
     * <li>所有分任务都完成，工单才算完成</li>
     * <li>分任务已上报和已下发的混合状态（可能还有完成的），工单状态：等待上级确认和下级上报</li>
     * <li>分任务全部上报（可能还有完成的），工单状态：等待上级确认</li>
     * <li>分任务全部已下发（可能还有完成的），工单状态：等待下级上报</li>
     * </p>
     *
     * @param subTaskInfos 该工单下所有分任务对象
     * @return 计算出来的工单状态
     */
    @Override
    public WorkStatusOfSubTaskEnum calculateWorkApplyStatusBySubTasks(final List<WorkSubTaskInfo> subTaskInfos) {
        // 总数
        int totalNumber = subTaskInfos.size();
        // 已下发、已上报、完成数量
        int issuedNumber = 0, reportedNumber = 0, finishNumber = 0;

        for (WorkSubTaskInfo subTask : subTaskInfos) {
            WorkSubTaskNodeEnum subTaskNode = subTask.getNode();
            // 任何一个分任务在办理/退回修改/等待确认/等待上报，那么忽略其他分任务的状态，直接判定工单就在办理环节
            if (subTaskNode == WorkSubTaskNodeEnum.WAITING_FOR_HANDLE
                    || subTaskNode == WorkSubTaskNodeEnum.REJECT
                    || subTaskNode == WorkSubTaskNodeEnum.WAITING_FOR_CONFIRM
                    || subTaskNode == WorkSubTaskNodeEnum.WAITING_FOR_REPORT) {
                return WorkStatusOfSubTaskEnum.SUBTASKS_ANY_HANDLE;
            }
            if (subTaskNode == WorkSubTaskNodeEnum.ISSUED) {
                issuedNumber++;
            } else if (subTaskNode == WorkSubTaskNodeEnum.SUPERIOR_CONFIRM) {
                reportedNumber++;
            } else if (subTaskNode == WorkSubTaskNodeEnum.FINISH) {
                finishNumber++;
            }
        }

        if (finishNumber == totalNumber) {
            return WorkStatusOfSubTaskEnum.SUBTASKS_ALL_FINISHED;
        }
        // 注意，下面几种状态都有可能混合了完成状态的分任务，但是完成状态的分任务已经不作为考虑的依据（因为已经完成了，所以就忽略了，只考虑在办的），因此只需要考虑上报的和下发的
        if (issuedNumber > 0) {
            return reportedNumber > 0 ? WorkStatusOfSubTaskEnum.SUBTASKS_ISSUED_AND_REPORTED : WorkStatusOfSubTaskEnum.SUBTASKS_ALL_ISSUED;
        } else {
            return WorkStatusOfSubTaskEnum.SUBTASKS_ALL_REPORTED;
        }
    }

    public void updateSubTasksNode(final List<WorkSubTaskInfo> subTaskInfoList, final WorkSubTaskNodeEnum targetNode) {
        final Date now = new Date();
        for (WorkSubTaskInfo subTaskInfo : subTaskInfoList) {
            // 不是目标状态的要改成目标状态
            if (subTaskInfo.getNode() != targetNode) {
                subTaskInfo.setNode(targetNode);
                subTaskInfo.setState(targetNode.getStateValue());
                subTaskInfo.setUpdateTime(now);
                workApplyInfoMapper.updateWorkSubTask(subTaskInfo);
            }
        }
    }

    /**
     * 在流程操作之前做的通用校验.
     *
     * @param workApplyInfo 工单信息
     * @param status        此流程操作的合法状态，工单应该在此状态
     * @param user          当前用户，工单的处理人应该是此用户
     */
    public void checkDataBeforeApprove(final WorkApplyInfo workApplyInfo, final WorkStatusEnum status, final LoginUserPojo user) {
        Preconditions.checkNotNull(workApplyInfo, "工单不能为空！");
        if (status != null) {
            Preconditions.checkArgument(workApplyInfo.getStatus() == status, "当前状态不能提交！");
        }
        Preconditions.checkNotNull(user, "操作用户不能为空！");
        Preconditions.checkArgument(StringUtils.isNotBlank(workApplyInfo.getHandlerUid()) && workApplyInfo.getHandlerUid().contains(user.getUserID()),
                "工单处理人非当前用户，无法操作！");
    }

    /**
     * 当子工单提交时，同步驱动父工单的流程流转，待办任务提交到下一节点确认环节.
     * <p>父工单的流程处理逻辑是：只要有任何一个下级分任务上报上来，就要流转到确认环节</p>
     * <p>如果上报上来的分任务已处理，还有其他没有上报上来的分任务，此时流程就停留在确认环节（有待办）</p>
     *
     * @param info    子工单对象
     * @param user    操作用户
     * @param opinion 意见
     * @return 父工单对象和操作后的任务列表
     */
    public Pair<WorkApplyInfo, List<TaskObject>> submitParentApplyWhenSubApplySubmit(final WorkApplyInfo info, final LoginUserPojo user,
                                                    final String opinion) {
        Preconditions.checkArgument(StringUtils.isNotBlank(info.getParentId()), "父流程id不存在！");
        // 加载父工单对象，注意这里不级联加载父工单下的分任务、附件等对象
        WorkApplyInfo parentApply = workApplyInfoMapper.findWorkApplyInfoById(info.getParentId());
        Preconditions.checkNotNull(parentApply, "父流程不存在，id:" + info.getParentId());

        // 父流程状态
        WorkStatusEnum parentApplyStatus = parentApply.getStatus();
        // 如果父工单已经是在确认环节（说明之前已经有别的分任务上报上来了），那么就无须重复操作了
        if (parentApplyStatus == WorkStatusEnum.RESPONDER_APPROVE
                || parentApplyStatus == WorkStatusEnum.SUB_ORG_USER_CONFIRM
                || parentApplyStatus == WorkStatusEnum.SUB_ORG_USER_HANDLE) {
            return Pair.of(parentApply, Lists.newArrayList());
        }

        // 校验父工单是否分任务子流程办理，是的话才需要处理流程
        Preconditions.checkArgument(parentApplyStatus == WorkStatusEnum.CREATE_SUB_FLOW
                        || parentApplyStatus == WorkStatusEnum.SUB_CREATE_SUB_FLOW,
                "父流程状态非创建子流程状态，id:" + info.getParentId());
        parentApply.setStatus(WorkApplyOperationUtils.getReportConfirmStatus(parentApply));
        parentApply.setHandlerName(info.getCreatorName());
        parentApply.setHandlerUid(info.getCreatorUid());
        parentApply.setUpdateTime(DateTime.now().toDate());
        workApplyInfoMapper.updateWorkApplyInfo(parentApply);
        //提交父流程的任务
        return Pair.of(parentApply, WorkApplyOperationUtils.completeUniflowConfirm(parentApply, opinion, uniFlowService));
    }

    /**
     * 更新工单.
     *
     * @param workApplyInfo 工单
     */
    public void updateWorkApplyFully(final WorkApplyInfo workApplyInfo) {
        WorkApplyInfo oldApplyInfo = this.findWorkApplyInfoById(workApplyInfo.getId());
        Preconditions.checkNotNull(oldApplyInfo, "待更新工单不存在！");
        workApplyInfo.setUpdateTime(DateTime.now().toDate());
        workApplyInfoMapper.updateWorkApplyInfo(workApplyInfo);

        // 提交管理员处理状态时，插入抄送信息
        if (Objects.equals(workApplyInfo.getStatus(), WorkStatusEnum.CREATE_SUB_FLOW)) {
            addCopySubmitInfo(workApplyInfo);
        }
        //分任务更新
        updateSubTasks(workApplyInfo, oldApplyInfo);
        //参考附件更新
        updateReferenceAttachments(workApplyInfo, oldApplyInfo);
        // 结果附件更新
        updateResultAttachment(workApplyInfo, oldApplyInfo);
    }

    /**
     * 分任务更新.
     *
     * @param workApplyInfo 当前工单
     * @param oldApplyInfo  原工单
     */
    public void updateSubTasks(final WorkApplyInfo workApplyInfo, final WorkApplyInfo oldApplyInfo) {
        // 先删除不需要的分任务
        deleteSubTaskAndAttachmentsForUpdate(workApplyInfo, oldApplyInfo);

        // 再添加新的、更新旧的
        saveOrUpdateSubTasksAndAttachment(workApplyInfo);
    }

    private void saveOrUpdateSubTasksAndAttachment(final WorkApplyInfo workApplyInfo) {
        if (CollectionUtils.isNotEmpty(workApplyInfo.getSubTasks())) {
            for(ListIterator<WorkSubTaskInfo> it = workApplyInfo.getSubTasks().listIterator(); it.hasNext(); ) {
                // 设置索引号，用来排序，保证存进去、取出来的顺序是一样的
                int orderIndex = it.nextIndex();
                WorkSubTaskInfo subTaskInfo = it.next();
                subTaskInfo.setOrderIndex(orderIndex);
                // 和主工单关联好
                subTaskInfo.setWorkApplyId(workApplyInfo.getId());
                subTaskInfo.setParentWorkApplyId(workApplyInfo.getParentId());
                // 单个分任务更新
                saveOrUpdateSingleSubTaskAndAttachment(subTaskInfo);
            }
        }
    }

    private void deleteSubTaskAndAttachmentsForUpdate(final WorkApplyInfo workApplyInfo, final WorkApplyInfo oldApplyInfo) {
        // 没东西要删就不要删了
        if(CollectionUtils.isEmpty(oldApplyInfo.getSubTasks())) {
            return;
        }

        // 新的分任务id列表
        final List<String> newIdList = workApplyInfo.getSubTasks().stream().map(WorkSubTaskInfo::getId).filter(Objects::nonNull).collect(Collectors.toList());
        // 找出老的、在新的列表里已经不存在的id，这些是需要删除的
        final List<String> needDeleteIdList = oldApplyInfo.getSubTasks().stream().map(WorkSubTaskInfo::getId).filter(id -> !newIdList.contains(id)).collect(Collectors.toList());

        // 没东西要删就不要删了
        if(CollectionUtils.isEmpty(needDeleteIdList)) {
            return;
        }

        // 先删除分任务附件
        workApplyInfoMapper.deleteAttachmentBySubTaskIdList(needDeleteIdList);
        // 再删除分任务对象本身
        workApplyInfoMapper.deleteSubTaskByIdList(needDeleteIdList);
    }

    private void saveOrUpdateSingleSubTaskAndAttachment(final WorkSubTaskInfo info) {
        if (StringUtils.isBlank(info.getId())) {
            // 这2个字段应该前端来设置，这里做个保底
            info.setId(UUID.randomUUID().toString());
            info.setNewAdd(true);
        }

        // 判断新增还是更新
        if(info.isNewAdd()) {
            workApplyInfoMapper.insertSubTask(info);
        } else {
            workApplyInfoMapper.updateWorkSubTask(info);
        }

        // 附件不用考虑删除和更新，直接新增，有多少附件新增多少附件
        // 删除：页面上点击删除，直接Ajax调用后端把附件删了
        // 更新：先删除、再上传，相当于是新增了
        attachmentService.insertAttachmentForSubTask(info);
    }

    /**
     * 参考附件更新.
     *
     * @param workApplyInfo 当前工单
     * @param oldApplyInfo  原工单
     */
    private void updateReferenceAttachments(final WorkApplyInfo workApplyInfo, final WorkApplyInfo oldApplyInfo) {
        // 参考附件更新
        if (CollectionUtils.isNotEmpty(workApplyInfo.getReferenceAttachments())) {
            List<WorkApplyInfoAttachment> newInsertAttaches = workApplyInfo.getReferenceAttachments().stream()
                    .filter(it -> StringUtils.isBlank(it.getId())).collect(Collectors.toList());
            for (WorkApplyInfoAttachment attachment : newInsertAttaches) {
                attachment.setId(UUID.randomUUID().toString());
                attachment.setWorkApplyId(workApplyInfo.getId());
                workApplyInfoMapper.insertWorkApplyInfoAttachment(attachment);
            }
        }
        // 参考附件检查删除
        /*
         * List<WorkApplyInfoAttachment> oldAttaches = oldApplyInfo.getAttachments(); if
         * (CollectionUtils.isNotEmpty(oldAttaches)) { Set<String> subAttachmentIds =
         * Optional.ofNullable(workApplyInfo.getAttachments()).map(it -> it.stream()
         * .map(WorkApplyInfoAttachment::getId).collect(Collectors.toSet())).orElse(Sets.newHashSet());
         * oldAttaches.stream().filter(it -> !subAttachmentIds.contains(it.getId())) .forEach(x ->
         * workApplyInfoMapper.deleteAttachmentById(x.getId())); }
         */
    }

    /**
     * 结果附件更新.
     *
     * @param workApplyInfo 当前工单
     * @param oldApplyInfo  原工单
     */
    private void updateResultAttachment(WorkApplyInfo workApplyInfo, WorkApplyInfo oldApplyInfo) {
        if (CollectionUtils.isNotEmpty(workApplyInfo.getReportAttachments())) {
            for (WorkApplyInfoAttachment info : workApplyInfo.getReportAttachments()) {
                if (StringUtils.isNotBlank(info.getId()) && info.getAttachment() != null) {
                    info.setId(info.getId());
                    info.setType(AttachTypeEnum.RESULT);
                    info.setWorkApplyId(workApplyInfo.getId());
                    workApplyInfoMapper.updateWorkApplyInfoAttachment(info);
                }
            }
        }
        // 结果附件检查删除
        /*
         * List<WorkApplyInfoAttachment> oldAttachs = oldApplyInfo.getResultAttachment(); if
         * (CollectionUtils.isNotEmpty(oldAttachs)) { Set<String> subAttachmentIds =
         * Optional.ofNullable(workApplyInfo.getResultAttachment()) .map(it -> it.stream()
         * .map(WorkApplyInfoAttachment::getId) .collect(Collectors.toSet())) .orElse(Sets.newHashSet());
         * oldAttachs.stream() .filter(it -> !subAttachmentIds.contains(it.getId())) .forEach(x ->
         * workApplyInfoMapper.deleteAttachmentById(x.getId())); }
         */
    }

    private void insertSubTaskResultAttachment(WorkSubTaskInfo info) {
        if (CollectionUtils.isEmpty(info.getReportAttachments())) {
            return;
        }
        for (WorkApplyInfoAttachment resAttachment : info.getReportAttachments()) {
            resAttachment.setId(UUID.randomUUID().toString());
            resAttachment.setWorkApplyId(info.getWorkApplyId());
            resAttachment.setSubTaskId(info.getId());
            resAttachment.setType(AttachTypeEnum.RESULT);
            workApplyInfoMapper.insertWorkApplyInfoAttachment(resAttachment);
        }
    }

    private void updateSubTaskResultAttachment(final WorkSubTaskInfo info,
                                               final WorkApplyInfo workApplyInfo) {
        if (CollectionUtils.isEmpty(info.getReportAttachments())) {
            return;
        }
        String subTaskId = info.getId();
        info.setId(subTaskId);
        info.setWorkApplyId(workApplyInfo.getId());
        for (WorkApplyInfoAttachment resAttachment : info.getReportAttachments()) {
            final String attachementId = Optional.of(resAttachment)
                    .filter(t -> t.getAttachment() != null).map(WorkApplyInfoAttachment::getId)
                    .orElse(null);
            resAttachment.setWorkApplyId(workApplyInfo.getId());
            resAttachment.setSubTaskId(subTaskId);
            resAttachment.setType(AttachTypeEnum.RESULT);
            if (StringUtils.isBlank(attachementId)) {
                resAttachment.setId(UUID.randomUUID().toString());
                workApplyInfoMapper.insertWorkApplyInfoAttachment(resAttachment);
            } else {
                resAttachment.setId(attachementId);
                workApplyInfoMapper.updateWorkApplyInfoAttachment(resAttachment);
            }
        }
    }

    /**
     * 查询工单信息.
     *
     * @param id 工单id
     * @return 工单信息
     */
    @Override
    public WorkApplyInfo findWorkApplyInfoById(String id) throws WorkApplyException {
        try {
            Preconditions.checkArgument(StringUtils.isNotBlank(id), "待查询工单的id不能为空！");
            WorkApplyInfo workApplyInfo = workApplyInfoMapper.findWorkApplyInfoById(id);
            Preconditions.checkNotNull(workApplyInfo, "待查询工单不存在");
            if(StringUtils.isBlank(workApplyInfo.getFlowType())) {
                workApplyInfo.setFlowType(BizConstant.WISDOM_MAIN_PROCESS_CODE);
            }
            switch (workApplyInfo.getFlowType()) {
                case BizConstant.CHAPTER_APPLY_PROCESS_CODE:
                    loadDetailForChapterApply(workApplyInfo, AttachTypeEnum.CHAPTER_FILE);
                    break;
                case BizConstant.PARTY_BRANCH_PROCESS_CODE:
                case BizConstant.PARTY_COMMITTEE_PROCESS_CODE:
                    loadDetailForChapterApply(workApplyInfo, AttachTypeEnum.SECRETARY_FILE);
                    break;
                default:
                    loadDetailForWorkApply(workApplyInfo, false);
            }
            return workApplyInfo;
        } catch (Exception e) {
            LOGGER.error("findWorkApplyInfoById error id:{}", id);
            throw new WorkApplyException("查询工单失败", e);
        }
    }

    /**
     * 通过父工单id查询子工单信息.
     *
     * @param parentId 父工单id
     * @return 工单信息
     */
    @Override
    public List<WorkApplyInfo> findWorkApplyByParentId(final String parentId) {
        try {
            Preconditions.checkArgument(StringUtils.isNotBlank(parentId), "待查询子工单的父工单id不能为空！");
            final List<WorkApplyInfo> workApplyInfos = workApplyInfoMapper.findWorkApplyInfoByParentApplyId(parentId);

            if (CollectionUtils.isNotEmpty(workApplyInfos)) {
                loadDetailForSubWorkApplys(parentId, workApplyInfos);
            }
            return workApplyInfos;
        } catch (Exception e) {
            LOGGER.error("findWorkApplyByParentId error parentId:{}", parentId);
            throw new WorkApplyException("查询子工单失败", e);
        }
    }

    /**
     * 加载子工单详情信息.
     * 之前的方法数据库查询次数太多了，大于子工单数量乘分任务数据
     * 修改后使查询次数成常数级.一次查询出需要的数据在内存中处理关系
     * 查询附件信息不查询blob字段
     * @param parentId 分工单id
     * @param subWorkApplyInfos 子工单信息列表
     */
    private void loadDetailForSubWorkApplys(final String parentId, final List<WorkApplyInfo> subWorkApplyInfos) {
        // 获取所有工单分任务信息
        final Map<String, List<WorkSubTaskInfo>> workApplySubTaskMap = workApplyInfoMapper.getWorkSubTaskInfoByParentTaskIdExcludeIds(parentId, null)
                .stream()
                .collect(Collectors.groupingBy(WorkSubTaskInfo::getWorkApplyId, Collectors.toList()));

        // 将工单对象映射成id和对象, 肯定是一一对应的
        final Map<String, WorkApplyInfo> workApplyInfoMap = subWorkApplyInfos
                .stream()
                .collect(Collectors.toMap(WorkApplyInfo::getId, Function.identity(), (a, b) -> a));

        // 查询所有分任务任务信息, 需要三次分组：工单、分任务、附件类型
        final Map<String, Map<String, Map<AttachTypeEnum, List<WorkApplyInfoAttachment>>>> applySubtaskAttachInfo =
                //查询子工单里只要看上报附件就行了，加类型可以减少查询返回的数量
                workApplyInfoMapper.findAttachmentExceptContentByWorkApplyIds(workApplyInfoMap.keySet(), AttachTypeEnum.RESULT)
                .stream()
                .collect(Collectors.groupingBy(WorkApplyInfoAttachment::getWorkApplyId,
                        Collectors.groupingBy(WorkApplyInfoAttachment::getSubTaskId,
                                Collectors.groupingBy(WorkApplyInfoAttachment::getType, Collectors.toList()))));

        //设置工单、分任务及附件
        workApplyInfoMap.entrySet()
                .stream()
                .forEach(entry -> {
                    final String workApplyId =  entry.getKey();

                    //分任务Id及对应附件信息
                    final Map<String, Map<AttachTypeEnum, List<WorkApplyInfoAttachment>>> subTaskWithAttachments = applySubtaskAttachInfo.getOrDefault(workApplyId, Collections.EMPTY_MAP);

                    // 工单肯定有分任务
                    final List<WorkSubTaskInfo> workSubTaskInfos = workApplySubTaskMap.get(workApplyId);

                    // 设置分任务的附件信息
                    workSubTaskInfos
                            .stream()
                            .forEach(subTask -> subTask.initResultAttachment(subTaskWithAttachments.getOrDefault(subTask.getId(), Collections.EMPTY_MAP)));

                    //设置工单分任务信息
                    final WorkApplyInfo workApplyInfo = entry.getValue();
                    workApplyInfo.setSubTasks(workSubTaskInfos);

                });

    }

    /**
     * 加载任务派发流程工单的详细信息.
     * @param workApplyInfo 任务派发流程工单
     * @param querySubApply 是否查询子工单，用于优化, 查询子工单时其实不需要参考附件和审批记录
     */
    private void loadDetailForWorkApply(final WorkApplyInfo workApplyInfo, final boolean querySubApply) {
        final String id = workApplyInfo.getId();
        // 加载分任务
        final List<WorkSubTaskInfo> subTasks = workApplyInfoMapper.findWorkSubTasksByApplyId(id);

        // 给每一个分任务加载附件
        loadAllAttachmentsForSubTask(subTasks);
        workApplyInfo.setSubTasks(subTasks);

        // 查询子工单到这就可以了
        if (querySubApply) {
            return;
        }

        // 给主工单加载附件，注意参考附件要用主工单id来加载，其他附件用自己的id加载
        final String mainWorkApplyId = workApplyInfo.getMainWorkApplyId();

        // 设置参考附件
        workApplyInfo.setReferenceAttachments(workApplyInfoMapper.findWorkApplyInfoAttachmentsByApplyId(mainWorkApplyId, AttachTypeEnum.REFERENCE));

        // 加载处理记录
        workApplyInfo.setWorkApproveOpinions(workApplyInfoMapper.findWorkApproveOpinionsByApplyId(id));
    }

    private void loadAllAttachmentsForSubTask(final List<WorkSubTaskInfo> subTasks) {
        /**
         * 20221122：原来的方案性能太差了，N个分任务就要查询 N*3 次，改成一次查询，在内存中设置分任务附件信息
         */
        //获取所有分任务
        final List<String> subTaskIds = subTasks.stream()
                .map(WorkSubTaskInfo::getId)
                .collect(Collectors.toList());

        //附件信息按分任务id和附件类型两次分组
        final Map<String, Map<AttachTypeEnum, List<WorkApplyInfoAttachment>>> subTaskIdAttachmentMap =
                workApplyInfoMapper.findAttachmentExceptContentBySubTaskIds(subTaskIds, null)
                        .stream()
                        .collect(Collectors.groupingBy(WorkApplyInfoAttachment::getSubTaskId, Collectors.groupingBy(WorkApplyInfoAttachment::getType, Collectors.toList())));

        //模板附件使用的是主任务的数据，还要重新加载
        final List<String> mainTaskIds = subTasks.stream()
                .map(WorkSubTaskInfo::getMainTaskId)
                .collect(Collectors.toList());
        final Map<String, WorkApplyInfoAttachment> mainTaskIdTemplate =
                workApplyInfoMapper.findAttachmentExceptContentBySubTaskIds(mainTaskIds, AttachTypeEnum.TEMPLATE)
                .stream()
                .collect(Collectors.toMap(WorkApplyInfoAttachment::getSubTaskId, Function.identity(), (a, b) -> a));

        // 遍历分任务对象，初始化附件信息，需要修改对象，没想到别的办法就用forEach了
        subTasks.stream()
                .forEach(subTask -> {
                    subTask.initAttachment(subTaskIdAttachmentMap.getOrDefault(subTask.getId(), Collections.EMPTY_MAP));

                    //设置模板附件
                    Optional.ofNullable(mainTaskIdTemplate.get(subTask.getMainTaskId()))
                                    .map(Arrays::asList)
                                    .ifPresent(list -> subTask.setTemplateAttachments(list));
                });

    }

    private void loadTemplateAttachments(final WorkSubTaskInfo subTask) {
        subTask.setTemplateAttachments(workApplyInfoMapper.findWorkApplyInfoAttachmentsBySubTaskId(subTask.getMainTaskId(), AttachTypeEnum.TEMPLATE));
    }

    private void loadReportAttachments(final WorkSubTaskInfo subTask) {
        subTask.setReportAttachments(workApplyInfoMapper.findWorkApplyInfoAttachmentsBySubTaskId(subTask.getId(), AttachTypeEnum.RESULT));
    }

    private void loadMergeAttachments(final WorkSubTaskInfo subTask) {
        subTask.setMergeAttachments(workApplyInfoMapper.findWorkApplyInfoAttachmentsBySubTaskId(subTask.getId(), AttachTypeEnum.MERGE));
    }

    /**
     * 加载用章申请流程工单的详细信息
     * @param workApplyInfo 用章申请流程工单
     */
    private void loadDetailForChapterApply(final WorkApplyInfo workApplyInfo, final AttachTypeEnum attachTypeEnum) {
        // 只要加载附件就可以了
        workApplyInfo.setAttachments(workApplyInfoMapper.findWorkApplyInfoAttachmentsByApplyId(
                workApplyInfo.getId(), attachTypeEnum));
        // 加载处理记录
        workApplyInfo.setWorkApproveOpinions(workApplyInfoMapper.findWorkApproveOpinionsByApplyId(workApplyInfo.getId()));
    }

    /**
     * 上报工单，实质是工单里的分任务要上报，分任务上报后要更新工单的状态和处理人。
     *
     * @param info    要上报的工单
     * @param taskId 流程平台任务id
     * @param subTaskInfos 分任务信息
     * @param user    当前用户
     * @param opinion 处理意见
     */
    @Override
    public void reportWorkApply(final WorkApplyInfo info, final String taskId,
                                final List<WorkSubTaskInfo> subTaskInfos, final LoginUserPojo user, final String opinion) {
        // 获得所有分任务列表
        final List<WorkSubTaskInfo> allSubTasks = workApplyInfoMapper.findWorkSubTasksByApplyId(info.getId());
        // 工单的分任务状态，根据所有分任务状态计算得到
        final WorkStatusOfSubTaskEnum workStatusOfSubTask = calculateWorkApplyStatusBySubTasks(allSubTasks);

        // 下一流程状态和处理人
        WorkStatusEnum nextWorkStatus = null;
        UserInfo nextHandler = null;
        // 决策名
        final String decision = "上报分任务";
        // 根据分任务状态来判断下一环节流转到哪里
        if (workStatusOfSubTask == WorkStatusOfSubTaskEnum.SUBTASKS_ALL_REPORTED) {
            // 发送到拟稿人确认环节
            nextWorkStatus = WorkStatusEnum.SUB_CREATOR_CONFIRM;
            nextHandler = new UserInfo(info.getCreatorUid(), info.getCreatorName());
            info.setStatus(nextWorkStatus);
            info.setHandlerName(null);
            info.setHandlerUid(null);
        } else if (workStatusOfSubTask == WorkStatusOfSubTaskEnum.SUBTASKS_ALL_ISSUED
                || workStatusOfSubTask == WorkStatusOfSubTaskEnum.SUBTASKS_ISSUED_AND_REPORTED) {
            // 等待下级上报，发送到分任务办理子流程
            nextWorkStatus = WorkStatusEnum.SUB_CREATE_SUB_FLOW;
            nextHandler = WorkApplyOperationUtils.AUTO_NODE_HANDLER;
            info.setStatus(nextWorkStatus);
            info.setHandlerName(null);
            info.setHandlerUid(null);
        } else if (workStatusOfSubTask == WorkStatusOfSubTaskEnum.SUBTASKS_ANY_HANDLE) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("分任务已上报，但还有其他分任务在处理中，因此不处理流程, workApplyId[{}], taskId[{}], user[{}]",
                        LoggerHelper.replaceBreakCharacters(info.getId()),
                        LoggerHelper.replaceBreakCharacters(taskId),
                        LoggerHelper.replaceBreakCharacters(user.getUserID()));
            }
        } else {
            // 这不可能，一定是哪里出问题了……
            throw new WorkApplyException(String.format("分任务上报出错，预料之外的分任务完成状态[%s], workApplyId[%s], taskId[%s], user[%s]",
                    workStatusOfSubTask, info.getId(), taskId, user.getUserID()));
        }
        info.setUpdateTime(DateTime.now().toDate());
        // 更新到数据库
        workApplyInfoMapper.updateWorkApplyInfo(info);

        // 处理流程，nextWorkStatus为null表示要停留在当前状态（因为还有其他分任务需要处理）、所以不需要处理流程
        if (nextWorkStatus != null) {
            ResultInfo jumpResult = uniFlowService.jump(taskId,
                    nextWorkStatus.getValue(),
                    new UserInfo[]{nextHandler},
                    WorkApplyOperationUtils.convertUserInfo(user),
                    decision);
            checkUniflowMethodInvokeResult(jumpResult, "分任务子流程上报完成任务失败， id：" + info.getId() + ",结果：");
            /*
             * JIRA:ZJYDDJJQR-383 上报时发邮件加配置如果“上报全部完成才触发通知”需要特殊处理，否则按原来的方式
             * 判断逻辑：
             *    获取父工单对应的所有子工单分任务信息
             *    判断状态是否全部满足已上报或者确认
             *    查询父工单（获取任务的tiid）的待办发邮件
             */
            final WorkApplyInfo parentWorkApplyInfo = workApplyInfoMapper.findWorkApplyInfoById(info.getParentId());
            judgeSubTaskAllReportAndNotify(info, subTaskInfos, parentWorkApplyInfo);
        }
    }

    /**
     * 判断所有分任务是否上报.
     * @param workApplyInfo 工单信息
     * @param subTaskInfos 任务信息
     * @param parentWorkApplyInfo 父工单信息
     */
    private void judgeSubTaskAllReportAndNotify(final WorkApplyInfo workApplyInfo,
                                                final List<WorkSubTaskInfo> subTaskInfos,
                                                final WorkApplyInfo parentWorkApplyInfo) {
        if (parentWorkApplyInfo.isNotifyAfterReportAll()) {
            // 内存中已经存在分任务ID
            final Set<String> excludeIds = subTaskInfos.stream().map(WorkSubTaskInfo::getId).collect(Collectors.toSet());
            // 在数据库中的分任务信息
            final List<WorkSubTaskInfo> subTaskInfoInDb = workApplyInfoMapper.getWorkSubTaskInfoByParentTaskIdExcludeIds(parentWorkApplyInfo.getId(), excludeIds);

            final boolean allReport = Stream.of(subTaskInfos, subTaskInfoInDb)
                    .flatMap(List::stream)
                    .allMatch(item -> REPORT_FINISH_NODE.contains(item.getNode()));
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("工单{}，上报全部完成：{}", workApplyInfo.getMainTaskName(), allReport);
            }
            if (!allReport) {
                return;
            }
        }

        final boolean mainApply = WorkApplyOperationUtils.isMainApply(parentWorkApplyInfo);
        final String business = mainApply ? BizConstant.WISDOM_MAIN_PROCESS_CODE : BizConstant.WISDOM_SUB_FLOW_PROCESS_CODE;
        final ResultInfo resultInfo = uniFlowService.queryTasksByApplyId(business, parentWorkApplyInfo.getApplyId());
        emailService.sendEmail(this::sendEmailFunction, (TaskObject[]) resultInfo.getData(), workApplyInfo);
    }

    /**
     * 创建子流程任务.
     *
     * @param info 工单信息，整个工单的所有分任务和办理人都要创建
     * @param user 操作用户
     * @return 任务集合
     */
    public Pair<List<TaskObject>, List<WorkApplyInfo>> createSubTask(final WorkApplyInfo info, final LoginUserPojo user) {
        return createSubTask(info, info.getSubTasks(), WorkApplyOperationUtils.getReceiverUsers(info), user);
    }

    public Pair<List<TaskObject>, List<WorkApplyInfo>> createSubTask(final WorkApplyInfo info, final List<WorkSubTaskInfo> subTasks, final String dispatchUserNames, final LoginUserPojo user) {
        return createSubTask(info, subTasks, WorkApplyOperationUtils.getReceiverUsers(dispatchUserNames), user);
    }

    /**
     * 创建子流程任务.
     *
     * @param info          工单信息
     * @param subTasks      要分派的分任务列表，可能只是当前工单下的部分分任务
     * @param dispatchUsers 要分派的人员名单（要分配给这些人）
     * @param user          操作用户
     * @return 任务集合
     */
    public Pair<List<TaskObject>, List<WorkApplyInfo>> createSubTask(final WorkApplyInfo info, final List<WorkSubTaskInfo> subTasks, final List<List<UserInfo>> dispatchUsers, final LoginUserPojo user) {
        final List<TaskObject> tasks = Lists.newArrayList();
        final List<WorkApplyInfo> workApplyInfos = Lists.newArrayList();
        try {
            Preconditions.checkArgument(StringUtils.isNotBlank(info.getReceiverUid()), "主送对象为空，无法创建子流程！");
            final String dispatchUid = StringUtils.isBlank(info.getParentId()) ? info.getCreatorUid() : user.getUserID();
            final String dispatchUserName = StringUtils.isBlank(info.getParentId()) ? info.getCreatorName() : user.getUserName();

            final List<Callable<String>> taskList = dispatchUsers.stream()
                    .map(dispatchUser -> (Callable<String>) () ->
                            processSubtask(dispatchUid, dispatchUserName, info, dispatchUser, subTasks, user, workApplyInfos, tasks))
                    .collect(Collectors.toList());

            final List<Future<String>> futures = createApplyThreadPoolExecutor.invokeAll(taskList);
            // future.get()方法使用foreach时，需要在循环里面再次捕获异常，不方便统一做异常处理
            for(final Future<String> future : futures) {
                final String result = future.get();
                if (StringUtils.isNotBlank(result)) {
                    throw new WorkApplyException("创建子流程任务失败，分派人信息:" + result);
                }
            }

            return Pair.of(tasks, workApplyInfos);
        } catch (final WorkApplyException | InterruptedException | ExecutionException e) {
            // 回滚ucc完成的任务
            WorkApplyOperationUtils.endSubFlow(uniFlowService, tasks, user);
            LOGGER.error("创建子流程任务失败，主工单id:{}", info.getId(), e);
            throw new WorkApplyException("创建子流程任务失败，主工单id:" + info.getId(), e);
        }
    }

    @Override
    public void initWorkApplyInfoAttachments(HttpServletRequest request, WorkApplyInfo workApplyInfo, LoginUserPojo user) {
        attachmentService.initWorkApplyInfoAttachments(request, workApplyInfo, user);
    }

    @Override
    public void handleReportAttachments(final List<WorkSubTaskInfo> subTasks, final LoginUserPojo user, final HttpServletRequest request) {
        // 将分任务拆分成2组：需要上传附件的，直接使用合并报表的（不需要上传）
        Map<Boolean, List<WorkSubTaskInfo>> subTaskMap =
                subTasks.stream().collect(Collectors.partitioningBy(WorkSubTaskInfo::isReportMergeAttachment));
        List<WorkSubTaskInfo> subTasksReportUpload = subTaskMap.get(Boolean.FALSE);
        List<WorkSubTaskInfo> subTasksReportMerge = subTaskMap.get(Boolean.TRUE);

        // 处理需要上传附件的
        if (CollectionUtils.isNotEmpty(subTasksReportUpload)) {
            attachmentService.handleReportAttachments(subTasksReportUpload, user, (MultipartHttpServletRequest) request);
        }
        // 处理直接利用合并报表的
        if (CollectionUtils.isNotEmpty(subTasksReportMerge)) {
            for (WorkSubTaskInfo subTaskInfo : subTasksReportMerge) {
                // 找出合并报表
                List<WorkApplyInfoAttachment> mergeAttachmentList = workApplyInfoMapper.findWorkApplyInfoAttachmentsBySubTaskId(subTaskInfo.getId(), AttachTypeEnum.MERGE);
                // 将合并报表转成结果报表 （要考虑是否已经有结果报表了（退回重新上报的情况））
                List<WorkApplyInfoAttachment> reportAttachmentList = mergeAttachmentList.stream()
                        .map(attachment -> makeOrUpdateReportAttachement(user, subTaskInfo, attachment))
                        .collect(Collectors.toList());
                subTaskInfo.setReportAttachments(reportAttachmentList);
            }
        }
    }

    /**
     * 创建或者更新上报附件.
     * @param user
     * @param subTaskInfo
     * @param attachment
     * @return
     */
    private WorkApplyInfoAttachment makeOrUpdateReportAttachement(LoginUserPojo user, WorkSubTaskInfo subTaskInfo, WorkApplyInfoAttachment attachment) {
        final List<WorkApplyInfoAttachment> reportAttachementList = workApplyInfoMapper.findWorkApplyInfoAttachmentsBySubTaskId(subTaskInfo.getId(), AttachTypeEnum.RESULT);
        if (CollectionUtils.isNotEmpty(reportAttachementList)) {
            // 目前需求模板和结果附件最多都只有一个
            final WorkApplyInfoAttachment workApplyInfoAttachment = reportAttachementList.get(0);
            workApplyInfoAttachment.setAttachment(attachment.getAttachment());
            workApplyInfoAttachment.setUploadTime(new Date());
            return workApplyInfoAttachment;
        }
        // 第一次上报的情况
        return makeReportAttachmentByMergeAttachment(attachment, subTaskInfo, user);

    }

    @Override
    public void insertMergeAttachments(WorkSubTaskInfo subTaskInfo) {
        attachmentService.insertMergeAttachment(subTaskInfo);
    }

    @Override
    public WorkApplyInfoAttachment findWorkAttachmentById(String attachmentId) {
        return attachmentService.findWorkAttachmentById(attachmentId);
    }

    /**
     * 判断是否挂起任务.
     * 用于确认或者退回后挂起待办.
     * @param currentApplyInfo 当前工单
     * @param subTasks 修改状态的分任务（子工单的分任务）
     */
    @Override
    public void checkNeedSuspendTask(final WorkApplyInfo currentApplyInfo, final List<WorkSubTaskInfo> subTasks) {
        /**
         * 判断状态要考虑两层分任务状态：
         * 当前工单的分任务状态: 如果是可上报的（主工单不需要再次上报），不用挂起
         * 所有子工单的分任务状态： 没有有可操作的分任务时，不用挂起。
         * 参数subTasks是子工单分任务，考虑分任务状态时需要考虑所有分任务
         * （子工单分任务处理后，肯定不会再处理了，实际只要考虑数据库中的分任务状态即可）
         */
         //判断当前工单的分任务状态
        final List<WorkSubTaskInfo> currentApplyInfoSubTasks = currentApplyInfo.getSubTasks();
        final boolean canReport = currentApplyInfoSubTasks.stream()
                .anyMatch(item -> CAN_REPORT_NODE.contains(item.getNode()));
        if (canReport) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("当前工单{}存在分任务可上报, 不需要挂起", currentApplyInfo.getMainTaskName());
            }
            return;
        }


        //下面是子工单的分任务状态判断
        //所有当前工单分任务id，用于查询子工单分任务
        final Set<String> ids = subTasks.stream()
            .map(WorkSubTaskInfo::getId)
            .collect(Collectors.toSet());

        final List<WorkSubTaskInfo> subTaskExceptCurrentTask = currentApplyInfoSubTasks
                .stream()
                .map(WorkSubTaskInfo::getId)
                .map(item -> workApplyInfoMapper.getWorkSubTaskInfoByParentTaskId(item))
                .flatMap(Collection::stream)
                .filter(subTask -> !ids.contains(subTask.getId()))
                .collect(Collectors.toList());

        /*
         * 2022-11-11 逐级退回的场景又有点特别
         * 上级退回后，如果有多个子工单分任务可以退回，则不用挂起
         */
        // 先获取当退回的工单分任务id
        final Set<String> rejectIds = currentApplyInfoSubTasks.stream()
                .filter(item -> WorkSubTaskNodeEnum.REJECT == item.getNode())
                .map(WorkSubTaskInfo::getId)
                .collect(Collectors.toSet());
        if (CollectionUtils.isNotEmpty(rejectIds)) {
            final boolean notAllReject = subTaskExceptCurrentTask.stream()
                    .anyMatch(item -> WorkSubTaskNodeEnum.REJECT != item.getNode());
            if (notAllReject) {
                return;
            }

        }
        // 根据数据库子工单分任务状态，判断是否有待办
        final boolean noToDoTask = subTaskExceptCurrentTask
                .stream()
                .noneMatch(subTask -> HAVE_TODO_NODE_AFTER_CONFIRM_OR_REJECT.contains(subTask.getNode()));

        // 判断是否有需要确认的子任务
        // && !subTaskInDb.stream().allMatch(subTask -> WorkSubTaskNodeEnum.FINISH == subTask.getNode())
        // 这个之前的目的是为了判断当前工单分任务状态，前面已经判断了，这里不用重复判断
        // 主工单都确认完后的情况，还有待办
        if (noToDoTask) {
            // 主工单分任务都确认或者退回后，主工单也是没有待办，此时要处理待办（待办隐藏）
            uniFlowService.suspendTask(currentApplyInfo.getTiid());
            if (StringUtils.equals(currentApplyInfo.getFlowType(), BizConstant.WISDOM_MAIN_PROCESS_CODE)) {
                currentApplyInfo.setStatus(WorkStatusEnum.CREATE_SUB_FLOW);
            } else {
                currentApplyInfo.setStatus(WorkStatusEnum.SUB_CREATE_SUB_FLOW);
            }
            currentApplyInfo.setHandlerUid(null);
            currentApplyInfo.setHandlerName(null);
            currentApplyInfo.setUpdateTime(new Date());
            workApplyInfoMapper.updateWorkApplyInfo(currentApplyInfo);
        }
    }

    /**
     * 给指定分任务产生下级分任务。
     * 注意，分任务的处理不走Uniflow，而是通过业务代码维护环节和状态，但不维护处理人，处理人在工单对象WorkApplyInfo中
     *
     * @param parentApply     父工单（并非父工单里所有分任务都要产生下级分任务）
     * @param subTaskInfoList 要产生下级分任务的分任务
     * @return 产生的分任务列表
     */
    private List<WorkSubTaskInfo> productLowerLevelSubTask(final WorkApplyInfo parentApply, final List<WorkSubTaskInfo> subTaskInfoList) {
        final Date now = new Date();
        return subTaskInfoList.stream().map(parentSubTask -> {
            WorkSubTaskInfo subTaskInfo = new WorkSubTaskInfo();
        // 复制属性
        BeanUtils.copyProperties(parentSubTask, subTaskInfo);
        subTaskInfo.setId(null);
        // 设置父工单ID
        subTaskInfo.setParentWorkApplyId(parentApply.getId());
        // 设置父任务ID
        subTaskInfo.setParentTaskId(parentSubTask.getId());
        // 设置主任务ID
        subTaskInfo.setMainTaskId(parentSubTask.getMainTaskId());
        // 设置分任务为待处理环节
        subTaskInfo.setNode(WorkSubTaskNodeEnum.WAITING_FOR_HANDLE);
        // 设置分任务为待办状态
        subTaskInfo.setState(ProcessStateEnum.TODO);
        // 清空附件，新造出来的分任务不需要任何附件，唯一需要的模板附件是直接引用主分任务的
        subTaskInfo.clearAllAttachment();
        subTaskInfo.setUpdateTime(now);
        // 设置更新标记，否则会执行update
        subTaskInfo.setNewAdd(true);
        // 把父级分任务设置为已下发环节、挂起状态
        parentSubTask.setNode(WorkSubTaskNodeEnum.ISSUED);
        parentSubTask.setState(ProcessStateEnum.SUSPEND);
        parentSubTask.setUpdateTime(now);
        return subTaskInfo;
    }).collect(Collectors.toList());
    }

    /**
     * 插入工单.
     *
     * @param workApplyInfo 工单
     */
    private void saveWorkInfo(final WorkApplyInfo workApplyInfo) {
        workApplyInfoMapper.insertWorkApplyInfo(workApplyInfo);

        saveOrUpdateSubTasksAndAttachment(workApplyInfo);
        insertAttachments(workApplyInfo);
        insertOpinions(workApplyInfo);
    }

    /**
     * 插入工单模板附件.
     *
     * @param templateAttachments 模板附件
     * @param workApplyId         工单号
     * @param subTaskId           分任务id
     */
    private void insertTemplateAttachments(final List<WorkApplyInfoAttachment> templateAttachments, String workApplyId, String subTaskId) {
        if (CollectionUtils.isEmpty(templateAttachments)) {
            return;
        }
        for (WorkApplyInfoAttachment resAttachment : templateAttachments) {
            resAttachment.setId(UUID.randomUUID().toString());
            resAttachment.setWorkApplyId(workApplyId);
            resAttachment.setSubTaskId(subTaskId);
            resAttachment.setType(AttachTypeEnum.TEMPLATE);
            workApplyInfoMapper.insertWorkApplyInfoAttachment(resAttachment);
        }
    }

    /**
     * 插入工单参考附件.
     *
     * @param workApplyInfo 工单
     */
    private void insertReferenceAttachments(final WorkApplyInfo workApplyInfo) {
        if (CollectionUtils.isNotEmpty(workApplyInfo.getReferenceAttachments())) {
            for (WorkApplyInfoAttachment attachment : workApplyInfo.getReferenceAttachments()) {
                attachment.setId(UUID.randomUUID().toString());
                attachment.setWorkApplyId(workApplyInfo.getId());
                workApplyInfoMapper.insertWorkApplyInfoAttachment(attachment);
            }
        }
    }

    /**
     * 插入工单.
     * @param workApplyInfo 工单
     * @param user          当前用户
     */
    public void insertWorkApply(final WorkApplyInfo workApplyInfo, LoginUserPojo user) {
        workApplyInfo.setId(UUID.randomUUID().toString());
        // 主工单就是它自己
        workApplyInfo.setMainWorkApplyId(workApplyInfo.getId());
        if (workApplyInfo.getCreateTime() == null) {
            workApplyInfo.setCreateTime(DateTime.now().toDate());
        }
        workApplyInfo.setUpdateTime(DateTime.now().toDate());
        workApplyInfo.setCreatorUid(user.getUserID());
        workApplyInfo.setCreatorName(user.getUserName());
        if (workApplyInfo.getStatus() == null) {
            workApplyInfo.setStatus(WorkStatusEnum.DRAFT);
        }

        // 插入拟稿抄送信息
        addCopySubmitInfo(workApplyInfo);

        // 更新工单
        workApplyInfoMapper.insertWorkApplyInfo(workApplyInfo);

        // 更新分任务
        saveOrUpdateSubTasksAndAttachment(workApplyInfo);

        insertReferenceAttachments(workApplyInfo);
        insertOpinions(workApplyInfo);
    }

    /**
     * 更新工单的ApplyId.
     *
     * @param info 工单
     * @param data 流程引擎返回的节点任务信息
     */
    public void updateWorkApplyIdAndPiid(final WorkApplyInfo info, final TaskObject[] data) {
        if (data.length > 0) {
            // 不管返回多少个任务都是同一个流程下的，因此此处只取一个任务的工单id和piid的即可
            TaskObject taskObject = data[0];
            info.setApplyId(taskObject.getApplyId());
            info.setPiid(taskObject.getPiid());
            this.workApplyInfoMapper.updateWorkApplyInfo(info);
        }
    }

    /**
     * 插入意见.
     *
     * @param workApplyInfo 工单
     */
    private void insertOpinions(final WorkApplyInfo workApplyInfo) {
        if (CollectionUtils.isEmpty(workApplyInfo.getWorkApproveOpinions())) {
            return;
        }
        for (WorkApproveOpinion opinion : workApplyInfo.getWorkApproveOpinions()) {
            if (StringUtils.isBlank(opinion.getId())) {
                opinion.setId(UUID.randomUUID().toString());
            }
            opinion.setWorkApplyId(workApplyInfo.getId());
            if (opinion.getApproveTime() == null) {
                opinion.setApproveTime(DateTime.now().toDate());
            }
            workApplyInfoMapper.insertWorkApproveOpinion(opinion);
        }
    }

    /**
     * 插入结果附件.
     *
     * @param workApplyInfo 工单
     */
    private void insertResultAttachment(WorkApplyInfo workApplyInfo) {
        if (CollectionUtils.isEmpty(workApplyInfo.getReportAttachments())) {
            return;
        }
        for (WorkApplyInfoAttachment resAttachment : workApplyInfo.getReportAttachments()) {
            if (StringUtils.isBlank(resAttachment.getId())) {
                resAttachment.setId(UUID.randomUUID().toString());
            }
            resAttachment.setWorkApplyId(workApplyInfo.getId());
            workApplyInfoMapper.insertWorkApplyInfoAttachment(resAttachment);
        }
    }

    /**
     * 插入参考附件.
     *
     * @param workApplyInfo 工单
     */
    private void insertAttachments(WorkApplyInfo workApplyInfo) {
        if (CollectionUtils.isNotEmpty(workApplyInfo.getReferenceAttachments())) {
            for (WorkApplyInfoAttachment attachment : workApplyInfo.getReferenceAttachments()) {
                if (StringUtils.isBlank(attachment.getId())) {
                    attachment.setId(UUID.randomUUID().toString());
                }
                attachment.setWorkApplyId(workApplyInfo.getId());
                workApplyInfoMapper.insertWorkApplyInfoAttachment(attachment);
            }
        }
    }

    /**
     * 插入分任务.
     *
     * @param workApplyInfo 工单
     */
    private void insertSubTask(WorkApplyInfo workApplyInfo) {
        if (CollectionUtils.isNotEmpty(workApplyInfo.getSubTasks())) {
            for (WorkSubTaskInfo info : workApplyInfo.getSubTasks()) {
                if (StringUtils.isBlank(info.getId())) {
                    info.setId(UUID.randomUUID().toString());
                }
                info.setWorkApplyId(workApplyInfo.getId());
                workApplyInfoMapper.insertSubTask(info);
                if (CollectionUtils.isNotEmpty(info.getReportAttachments())) {
                    for (WorkApplyInfoAttachment resAttachment : info.getReportAttachments()) {
                        resAttachment.setId(UUID.randomUUID().toString());
                        resAttachment.setWorkApplyId(workApplyInfo.getId());
                        resAttachment.setType(AttachTypeEnum.REFERENCE);
                        resAttachment.setSubTaskId(info.getId());
                        workApplyInfoMapper.insertWorkApplyInfoAttachment(resAttachment);
                    }
                }

            }
        }
    }

    /**
     * 判断ucc接口调用结果.
     *
     * @param resultInfo 调用结果对象
     * @param prefix     错误提示语前缀
     */
    private void checkUniflowMethodInvokeResult(ResultInfo resultInfo, String prefix) {
        Preconditions.checkNotNull(resultInfo, "调用ucc接口返回结果为空！");
        Preconditions.checkArgument(resultInfo.isResult(), prefix + resultInfo.getInfo());
    }

    /**
     * 邮件发送
     */
    private void sendEmailFunction(Map<String, Object> map) {
        String title = "党建机器人-任务派发流程：{0}";
        final String contentTemplate = "<a href=\"{0}\">请处理党建机器人-任务派发流程任务工单：{1}</a>"
                + "<br/>发起人：{2}"
                + "<br/>发起时间：{3}";
        emailService.sendEmailFunction(title, contentTemplate, BizConstant.WISDOM_MAIN_PROCESS_CODE, talkConfig.getSiteUrl(), map);
    }

    /**
     * 新增抄送信息
     * @param workApplyInfo 主工单信息
     */
    @Override
    public void addCopySubmitInfo(final WorkApplyInfo workApplyInfo) {
        if (StringUtils.isNotBlank(workApplyInfo.getCopySubmit())) {
            List<WorkCopySubmitInfo> workCopySubmitInfos = JSON.parseArray(workApplyInfo.getCopySubmit()).stream().map(item -> {
                final JSONObject jsonObject = JSON.parseObject(String.valueOf(item));
                final WorkCopySubmitInfo workCopySubmitInfo = new WorkCopySubmitInfo();
                workCopySubmitInfo.setId(UUID.randomUUID().toString());
                workCopySubmitInfo.setWorkApplyId(workApplyInfo.getId());
                workCopySubmitInfo.setReadType(WorkApplyReadTypeEnum.TO_READ.getValue());
                workCopySubmitInfo.setCreateTime((DateTime.now().toDate()));
                workCopySubmitInfo.setUpdateTime((DateTime.now().toDate()));
                workCopySubmitInfo.setCopySubmitUid(String.valueOf(jsonObject.get("id")));
                workCopySubmitInfo.setCopySubmitName(String.valueOf(jsonObject.get("label")));

                return workCopySubmitInfo;
            }).collect(Collectors.toList());

            // 插入抄送信息表
            if (CollectionUtils.isNotEmpty(workCopySubmitInfos)) {
                workApplyInfoMapper.insertCopySubmitInfo(workCopySubmitInfos);
            }
        }
    }


    /**
     * 分派任务创建，子任务流程创建
     * @param dispatchUid 分派人员id
     * @param dispatchUserName 分派人员名字
     * @param info 工单信息
     * @param dispatchUser 分派人员
     * @param subTasks  子任务列表
     * @param user 操作用户信息
     * @param workApplyInfos 工单信息列表
     * @param tasks 流程任务返回数据
     * @throws Exception 内部的方法异常直接抛出的Exception
     */
    private String processSubtask(final String dispatchUid,
                                final String dispatchUserName,
                                final WorkApplyInfo info,
                                final List<UserInfo> dispatchUser,
                                final List<WorkSubTaskInfo> subTasks,
                                final LoginUserPojo user,
                                final List<WorkApplyInfo> workApplyInfos,
                                final List<TaskObject> tasks) {
        try {
            // 复制对象，设置基本属性
            final WorkApplyInfo subInfo = new WorkApplyInfo();
            BeanUtils.copyProperties(info, subInfo);
            subInfo.setDispatched(false);
            subInfo.setUpdateTime(null);
            subInfo.setCreateTime(DateTime.now().toDate());
            subInfo.setCreatorUid(dispatchUid);
            subInfo.setCreatorName(dispatchUserName);
            subInfo.setReceiverUid(null);
            subInfo.setReceiverName(null);
            subInfo.setStatus(WorkStatusEnum.SUB_ORG_USER_HANDLE);
            subInfo.setId(UUID.randomUUID().toString());
            subInfo.setMainTaskName(info.getMainTaskName());
            subInfo.setParentId(info.getId());
            // 主工单id，没有就取当前（当前就是主工单），用来关联参考附件
            subInfo.setMainWorkApplyId(StringUtils.isBlank(info.getMainWorkApplyId()) ? info.getId() : info.getMainWorkApplyId());
            subInfo.setFlowType(BizConstant.WISDOM_SUB_FLOW_PROCESS_CODE);

            // 产生分任务，分任务的流转不走Uniflow，而是通过业务代码自行维护
            subInfo.setSubTasks(productLowerLevelSubTask(info, subTasks));
            // 创建工单时，保存审批记录
            subInfo.setWorkApproveOpinions(Lists.newArrayList(
                    WorkApplyOperationUtils.buildApproveOpinionNew(info.getOpinion(),
                            user, info, "分派", WorkStatusEnum.DEPT_ADMIN_APPROVE.toString())));
            // 清空上报附件和参考附件（参考附件不用复制了，直接用父工单的）
            subInfo.setReportAttachments(Lists.newArrayList());
            subInfo.setReferenceAttachments(Lists.newArrayList());

            // 设置子工单处理人等信息
            final String dispatchUserUids = dispatchUser.stream().map(UserInfo::getUserId).collect(Collectors.joining(Constants.STRING_SEMICOLON));
            final String dispatchUserNames = dispatchUser.stream().map(UserInfo::getUserName).collect(Collectors.joining(Constants.STRING_SEMICOLON));
            subInfo.setHandlerUid(dispatchUserUids);
            subInfo.setHandlerName(dispatchUserNames);
            subInfo.setAssignUserId(dispatchUserUids);
            subInfo.setAssignUserName(dispatchUserNames);
            // 保持子工单，连里面的分任务一起保存了
            this.saveWorkInfo(subInfo);

            // 开始发起流程
            final ResultInfo resultInfo = this.uniFlowService.startAndJumpTask(BizConstant.WISDOM_SUB_FLOW_PROCESS_CODE,
                    new UserInfo(dispatchUid, dispatchUserName),
                    WorkApplyOperationUtils.getUniflowApplyId(subInfo),
                    subInfo.getTitle(), WorkStatusEnum.SUB_ORG_USER_HANDLE.getValue(),
                    dispatchUser.toArray(new UserInfo[0]), null);
            checkUniflowMethodInvokeResult(resultInfo, "创建智办子流程失败：");

            // 子流程流程启动成功后，更新业务工单的piid和applyId，便于关联业务信息和流程信息
            this.updateWorkApplyIdAndPiid(subInfo, (TaskObject[]) resultInfo.getData());
            workApplyInfos.add(subInfo);
            emailService.sendEmail(this::sendEmailFunction, (TaskObject[]) resultInfo.getData(), info);
            CollectionUtils.addAll(tasks, (TaskObject[]) resultInfo.getData());
            return null;
        } catch (final Exception e) { // 方法内部抛出的Exception
            LOGGER.error("创建子流程任务失败，dispatchUser:{}", dispatchUser, e);
            return dispatchUser.toString();
        }

    }
}
